package taskhandler

import (
	"fmt"
	"net/http"
	"strings"

	"github.com/astaxie/beego"
	"github.com/astaxie/beego/logs"
	"github.com/opensourceways/server-common-lib/utils"

	"cvevulner/common"
	"cvevulner/models"
)

const (
	packageUrl = "https://magicapi.osinfra.cn/repo/versions"
	pageSize   = 1000
)

// SyncEulerPackages sync packages of openeuler
func SyncEulerPackages() error {
	data, err := getEulerPackages()
	if err != nil {
		return err
	}

	handle := func(path string, detail []BranchDetail) {
		originByVersion := groupByVersion(path, detail)
		for _, origin := range originByVersion {
			if err = saveData(origin); err != nil {
				logs.Error("save data failed: ", err.Error())
			}
		}
	}

	for _, v := range data {
		for k, detail := range v {
			handle(k, detail)
		}
	}

	return nil
}

func saveData(origin models.OpenEulerRepoOrigin) error {
	data, err := models.FindEulerRepoOrigin(origin.PackageName, origin.Version)
	if err == nil {
		if err = models.DeleteEulerRepoOrigin(data.Id); err != nil {
			logs.Error("DeleteEulerRepoOrigin ", origin.PackageName, err.Error())
		}
	}
	if _, err = models.InsertEulerRepoOrigin(&origin); err != nil {
		logs.Error("InsertEulerRepoOrigin ", origin.PackageName, err.Error())
	}

	gitOpenEuler, err := models.FindGitOpenEuler(origin.PackageName, origin.Version)
	if err == nil {
		if err = models.DeleteGitOpenEuler(gitOpenEuler.GitId); err != nil {
			logs.Error("DeleteGitOpenEuler ", origin.PackageName, err.Error())
		}
	}
	goeId, err := models.InsertYamlData(&models.GitOpenEuler{
		PackageName: origin.PackageName,
		Version:     origin.Version,
		PackageId:   0,
		TableId:     0,
		Status:      1,
		Source:      "openeuler",
	})
	if err != nil {
		logs.Error("InsertYamlData ", origin.PackageName, err.Error())
	}

	gitPackageInfo, err := models.FindGitPackageInfo(origin.PackageName, origin.Version)
	if err == nil {
		if err = models.DeleteGitPackageInfo(gitPackageInfo.DetailId); err != nil {
			logs.Error("DeleteGitPackageInfo ", origin.PackageName, err.Error())
		}
	}
	_, err = models.InsertGitPackageInfo(&models.GitPackageInfo{
		GitId:       goeId,
		PackageName: origin.PackageName,
		Version:     origin.Version,
		Summary:     origin.Summary,
		Decription:  origin.Decription,
		Status:      0,
	})
	if err != nil {
		logs.Error("InsertGitPackageInfo ", origin.PackageName, err.Error())
	}

	return nil
}

// BranchDetail struct of response data
type BranchDetail struct {
	Summary     string      `json:"summary"`
	PackageName string      `json:"package_name"`
	Description interface{} `json:"description"`
	Version     string      `json:"version"`
	BRName      string      `json:"brname"`
}

func (d BranchDetail) isValid() bool {
	outSyncBranch := beego.AppConfig.String("cve::out_of_sync_branch")
	outSyncBranchList := strings.Split(outSyncBranch, ",")
	for _, v := range outSyncBranchList {
		if v == d.BRName {
			return false
		}
	}

	if d.PackageName == "webkit2gtk3" {
		return false
	}

	if d.PackageName != "" && d.Version != "" && !strings.Contains(d.Version, "%") {
		return true
	}

	return false
}

func (d BranchDetail) generateDescription() string {
	switch d.Description.(type) {
	case []interface{}:
		var t []string
		for _, v := range d.Description.([]interface{}) {
			t = append(t, v.(string))
		}

		return strings.Join(t, ",")
	case string:
		return d.Description.(string)
	default:
		return ""
	}
}

func groupByVersion(path string, detail []BranchDetail) map[string]models.OpenEulerRepoOrigin {
	split := strings.Split(path, "/")
	if len(split) < splitLen {
		logs.Error("path error", path)

		return nil
	}

	originByVersion := make(map[string]models.OpenEulerRepoOrigin)
	for _, d := range detail {
		if !d.isValid() {
			continue
		}

		if ov, ok := originByVersion[d.Version]; ok {
			ov.Branchs += fmt.Sprintf(",%s", d.BRName)

			if ov.Summary == "" {
				ov.Summary = d.Summary
			}

			if ov.Decription == "" {
				ov.Decription = d.generateDescription()
			}

			originByVersion[d.Version] = ov
		} else {
			originByVersion[d.Version] = models.OpenEulerRepoOrigin{
				PackageName: d.PackageName,
				Version:     d.Version,
				Summary:     d.Summary,
				Decription:  d.generateDescription(),
				Branchs:     d.BRName,
				Status:      1,
				CreateTime:  common.GetCurTime(),
				UpdateTime:  common.GetCurTime(),
				Repo:        split[1],
			}
		}
	}

	return originByVersion
}

// Response struct of response
type Response struct {
	Code    int                         `json:"code"`
	Message string                      `json:"message"`
	Data    []map[string][]BranchDetail `json:"data"`
}

func getEulerPackages() ([]map[string][]BranchDetail, error) {
	const retryTimes = 3

	cli := utils.NewHttpClient(retryTimes)
	var result []map[string][]BranchDetail
	page := 1
	for {
		url := fmt.Sprintf("%s?community=openeuler&pageSize=%d&page=%d", packageUrl, pageSize, page)
		req, err := http.NewRequest(http.MethodGet, url, nil)
		if err != nil {
			return nil, err
		}

		var resp Response
		if _, err = cli.ForwardTo(req, &resp); err != nil {
			return nil, err
		}

		result = append(result, resp.Data...)

		if len(resp.Data) < pageSize {
			break
		}

		page++
	}

	return result, nil
}
